package utils;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * The Class XmlSerializationUtils contains methods to convert an generic object to a XML format, to convert
 * a XML format to a generic object and a lot of useful methods to manipulate and work with XML DOM Documents.
 */
public class XmlSerializationUtils {
	
	/** The default package on which will be the message implementations and the conversion methods.*/
	private static final String _defaultPackage = "messages";

	/**
	 * This method is responsible to convert a given object to a DOM Element to be appended to a Document.
	 * That method could handle any object that implements the Interface Convertible. This is done using the
	 * Java language reflection mechanism, where to a object given, in runtime, we see the class to which it
	 * belongs and invoke the conversion method implemented in the Interface Convertible over the instance that
	 * had been given as argument.
	 * 
	 * @param data the object to be converted.
	 * @param doc the DOM Document to which the Element generated by this tag will belong.
	 * 
	 * @return the Element generated by converting the specified Object.
	 */
	public static Element getConversionObjectXml(Object data, Document doc){
		try {
			Method method = data.getClass().getMethod("convertToCanonic", Document.class);

			Element element = (Element) method.invoke(data, doc);
			
			return element;
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			System.out.println("A classe n�o suporta o metodo de convers�o."+ data.getClass());
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * This method is responsible for converting an element given into the original object. For this is given 
	 * as argument the Document Element and the method analyzes the first Element tag of it. Analyzing this
	 * tag will obtain the class which the name is identical to the tag given and will call the
	 * convertFromCanonic method of the Interface convertible over a new Instance of the Class found.
	 * 
	 * @param el the XML Element to be backward converted.
	 * 
	 * @return the original object converted from the element given.
	 */
	public static Object getConversionXmlObject(Element el){

		try {
			String nodeName = el.getTagName();
			Class<?> nodeClass = Class.forName(_defaultPackage+"."+nodeName);
			Method method = nodeClass.getMethod("convertFromCanonic",Element.class);
			Object obj = method.invoke(nodeClass.newInstance(), el);
			return obj;
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Given a object that had been analyzed and had been determined that is of the class List converts
	 * it to a XML format, either it having a known primitive type or a Object of the package Messages
	 * that implements the Interface Convertible as generic type.
	 * 
	 * @param _data the List to be converted.
	 * @param doc the DOM Document to which this Element will belong.
	 * 
	 * @return the Element representing the List given as argument.
	 */
	public static Element getListConversionObjectXml(Object _data, Document doc){
		Element xmlElement = doc.createElement("List");
		List<?> list = (List<?>) _data;
		for(Object listElement:list){
			if(listElement instanceof String){
				Element listEntry = doc.createElement("Entry");
				listEntry.setAttribute("String", (String) listElement);
				xmlElement.appendChild(listEntry);
			}
			else{
				Element xmlListElement = getConversionObjectXml(listElement, doc);
				xmlElement.appendChild(xmlListElement);
			}				
		}
		return xmlElement;
	}

	/**
	 * This method makes the backward conversion of a XML List, that could be founded by the tag List, into
	 * a Object List, either this list being from a primitive type or from a Object of the messages package.
	 * 
	 * @param element the Element to be backward converted.
	 * 
	 * @return the List converted from the Element given as argument.
	 */
	public static List<?> getListConversionXmlObject(Element element){
		NodeList nodeList = element.getChildNodes();
		List<Object> list = new LinkedList<Object>();
		if(nodeList.getLength()==0)
			return list;
		else if(((Element)nodeList.item(0)).getTagName().compareTo("Entry")==0){
			for(int idx = 0; idx < nodeList.getLength(); idx++){
				Element listEntry = (Element) nodeList.item(idx);
				String entryText = listEntry.getAttribute("String");
				list.add(entryText);
			}
			return list;
		} else {
			for(int idx = 0; idx < nodeList.getLength(); idx++){
				Element listEntry = (Element) nodeList.item(idx);
				Object obj = getConversionXmlObject(listEntry);
				list.add(obj);
			}
			return list;
		}
	}
	
	/**
	 * Given a XML DOM Document converts it to a String to could be printed and/or sent by the network 
	 * layer.
	 * 
	 * @param doc the Document to be converted to a String.
	 * 
	 * @return the String representing the Document converted.
	 */
	public static String xmlToString(Document doc){
		try {
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "no");			

			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(doc);
			trans.transform(source, result);

			return sw.toString();
		} catch (TransformerException e) {
			e.printStackTrace();
		}

		return null;
	}

	
	/**
	 * Given a String that is known that contains a XML Document converts it backwards to a XML Document.
	 * 
	 * @param xml the String who is known that contains XML text.
	 * 
	 * @return the Document reconstructed from the given String.
	 */
	public static Document stringToXml(String xml){

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		DocumentBuilder db;

		try {
			dbf.setIgnoringComments(true);  
			dbf.setIgnoringElementContentWhitespace(true);
			db = dbf.newDocumentBuilder();

			Document doc = db.parse(new InputSource(new StringReader(xml)));
			doc.normalizeDocument();

			return doc;
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;			
	}

	/**
	 * Given an element or a tag name gets its text value being it a child element of the given element
	 * or a attribute of it.
	 * 
	 * @param el the Element in which we want to obtain the text value.
	 * @param tagName the name of the Tag we want to get.
	 * 
	 * @return the value of the specified Tag on the given Element or null if the tag doesn't exists.
	 */
	public static String getTextValue(Element el, String tagName) {
		String textVal = null;
		NodeList nl = el.getElementsByTagName(tagName);
		if(nl != null && nl.getLength() > 0) {
			Element elmnt = (Element)nl.item(0);
			if(elmnt.hasChildNodes())
				textVal = elmnt.getFirstChild().getNodeValue();
		}//else{
			//textVal = nl.item(0).getNodeValue();
		//}

		try {
			return new String(textVal.getBytes(),"UTF-8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Gets the Integer value of the Tag specified by the tagName in the specified Element.
	 * 
	 * @param el the Element in which we want to find the tag.
	 * @param tagName the name of the tag we want to get.
	 * 
	 * @return the Integer value of the given tag or null if it's impossible to determines it.
	 * 
	 */
	public static Integer getIntValue(Element el, String tagName) {

		try{
			int number = Integer.parseInt(getTextValue(el,tagName));
			return number;
		}catch(NumberFormatException e){
			return null;
		}		
	}
	
	/**
	 * Gets the Long value of the Tag specified by the tagName in the specified Element.
	 * 
	 * @param el the Element in which we want to find the tag.
	 * @param tagName the name of the tag we want to get.
	 * 
	 * @return the Long value of the given tag or null if it's impossible to determines it.
	 * 
	 */
	public static Long getLongValue(Element el, String tagName) {

		try{
			long number = Long.parseLong(getTextValue(el,tagName));
			return number;
		}catch(NumberFormatException e){
			return null;
		}		
	}

	/**
	 * Gets the Boolean value of the Tag specified by the tagName in the specified Element.
	 * 
	 * @param el the Element in which we want to find the tag.
	 * @param tagName the name of the tag we want to get.
	 * 
	 * @return the Boolean value of the given tag.
	 * @see java.lang.Boolean#parseBoolean(String)
	 */
	public static Boolean getBooleanValue(Element el, String tagName){
		return Boolean.parseBoolean(getTextValue(el, tagName));		
	}	
}
